# For manipulating the datasets
library(dplyr)
library(readr)

# For plotting correlation matrix
library(ggcorrplot)


# Machine Learning library
library(caret)
# For Multi-core processing support
library(doMC)
# Use 3 cores
registerDoMC(cores=3)

GET THE DATA

Load the datasets

THE WINE QUALITY DATASET

The two datasets are related to red and white variants of the Portuguese “Vinho Verde” wine. For more details, consult the reference [Cortez et al., 2009]. Due to privacy and logistic issues, only physicochemical (inputs) and sensory (the output) variables are available (e.g. there is no data about grape types, wine brand, wine selling price, etc.).

These datasets can be viewed as classification or regression tasks. The classes are ordered and not balanced (e.g. there are much more normal wines than excellent or poor ones).

Available at https://archive.ics.uci.edu/ml/datasets/wine+quality

winedataset_blanco <- read_csv("data/blanco_train.csv.gz")
Parsed with column specification:
cols(
  `fixed acidity` = col_double(),
  `volatile acidity` = col_double(),
  `citric acid` = col_double(),
  `residual sugar` = col_double(),
  chlorides = col_double(),
  `free sulfur dioxide` = col_double(),
  `total sulfur dioxide` = col_double(),
  density = col_double(),
  pH = col_double(),
  sulphates = col_double(),
  alcohol = col_double(),
  quality = col_integer()
)
winedataset_red <- read_csv("data/tinto_train.csv.gz")
Parsed with column specification:
cols(
  `fixed acidity` = col_double(),
  `volatile acidity` = col_double(),
  `citric acid` = col_double(),
  `residual sugar` = col_double(),
  chlorides = col_double(),
  `free sulfur dioxide` = col_double(),
  `total sulfur dioxide` = col_double(),
  density = col_double(),
  pH = col_double(),
  sulphates = col_double(),
  alcohol = col_double(),
  quality = col_integer()
)
# Create a new feature for the type 
winedataset_blanco$type="white"
winedataset_red$type="red"

# Merge both datasets into one.
winedataset<-rbind(winedataset_blanco,winedataset_red)



# Print the dataset
winedataset


#winedataset %>% map(is.null)
winedataset %>% group_by(quality) %>% summarise(total=n())
winedataset %>% group_by(`total sulfur dioxide`,quality)  %>% summarise(total=n())

VISUALIZE THE DATA

reshape2::melt(winedataset) %>%
ggplot()+
  geom_boxplot(aes(x=variable,y=value,fill=variable))+
  theme_bw()+ 
  theme(axis.text.x = element_text(angle = 45, hjust = 1))+
  theme(legend.position = "none") 
Using type as id variables

  
  
plotly::ggplotly()

Correlation Matrix

#Matriz de correlacion

cor_matrix<-cor(winedataset %>% select(-type))
ggcorrplot(cor_matrix)

pairs(winedataset %>% select(-type))

Boxplot volatile

Boxplot alcohol

CLEAN, PREPARE & MANIPULATE THE DATA

Create categorical features (optional)

Create category labels for quality (optional)

No features created, quality as factor

trainset <- winedataset
trainset$quality <- as.factor(trainset$quality)

Eliminate type


trainset <- trainset %>% select(-type)
names(trainset) %>% as.data.frame()

TRAIN THE MODEL

Split train and test


trainIndex <- createDataPartition(as.factor(trainset$quality), p=0.80, list=FALSE)
data_train <- trainset[ trainIndex,]
data_test <-  trainset[-trainIndex,]
colnames(data_train) <- make.names(colnames(data_train))
colnames(data_test) <- make.names(colnames(data_test))

Plot class distribution in train

data_train  %>% group_by(quality) %>% summarise(total=n()) %>%
  ggplot()+
  geom_col(aes(x=quality,y=total,fill=quality))+
  theme_classic()

Plot class distribution in test

data_test  %>% group_by(quality) %>% summarise(total=n()) %>%
  ggplot()+
  geom_col(aes(x=quality,y=total,fill=quality))+
  theme_classic()

Feature selection

Train model

ctrl_fast <- trainControl(method="cv", 
                     repeats=1,
                     number=5, 
                   #  summaryFunction=twoClassSummary,
                     verboseIter=T,
                     classProbs=F,
                     allowParallel = TRUE)  
`repeats` has no meaning for this resampling method.
rfFitupsam$finalModel

Call:
 randomForest(x = x, y = y, mtry = param$mtry) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 2

        OOB estimate of  error rate: 33.34%
Confusion matrix:
  3  4   5    6   7  8 9 class.error
3 0  1  14    7   0  0 0   1.0000000
4 1 18  76   43   2  0 0   0.8714286
5 0  2 955  404   3  0 0   0.2998534
6 0  2 302 1415  95  0 0   0.2199559
7 0  0  16  322 351  3 0   0.4927746
8 0  0   1   55  34 34 0   0.7258065
9 0  0   0    3   1  0 0   1.0000000
importance <- varImp(rfFitupsam, scale=FALSE)
plot(importance)

TEST THE DATA

predsrfprobsamp=predict(rfFitupsam,data_test)
# use for regresion
#confusionMatrix(as.factor(predsrfprobsamp %>% round()),as.factor(data_test$quality))

confusionMatrix(predsrfprobsamp,as.factor(data_test$quality))
Confusion Matrix and Statistics

          Reference
Prediction   3   4   5   6   7   8   9
         3   0   1   0   0   0   0   0
         4   0   6   6   0   0   0   0
         5   3  15 239  89   5   0   0
         6   1  10  94 324  69   9   0
         7   1   2   2  39  99  13   1
         8   0   0   0   1   0   8   0
         9   0   0   0   0   0   0   0

Overall Statistics
                                         
               Accuracy : 0.6519         
                 95% CI : (0.622, 0.6809)
    No Information Rate : 0.4368         
    P-Value [Acc > NIR] : < 2.2e-16      
                                         
                  Kappa : 0.4638         
                                         
 Mcnemar's Test P-Value : NA             

Statistics by Class:

                      Class: 3 Class: 4 Class: 5 Class: 6 Class: 7 Class: 8  Class: 9
Sensitivity          0.0000000 0.176471   0.7009   0.7152  0.57225 0.266667 0.0000000
Specificity          0.9990310 0.994018   0.8391   0.6866  0.93287 0.999007 1.0000000
Pos Pred Value       0.0000000 0.500000   0.6809   0.6391  0.63057 0.888889       NaN
Neg Pred Value       0.9951737 0.972683   0.8513   0.7566  0.91591 0.978599 0.9990357
Prevalence           0.0048216 0.032787   0.3288   0.4368  0.16683 0.028930 0.0009643
Detection Rate       0.0000000 0.005786   0.2305   0.3124  0.09547 0.007715 0.0000000
Detection Prevalence 0.0009643 0.011572   0.3385   0.4889  0.15140 0.008679 0.0000000
Balanced Accuracy    0.4995155 0.585244   0.7700   0.7009  0.75256 0.632837 0.5000000
#confusionmat <- table(predsrfprobsamp %>% round(),as.factor(data_test$quality))

confusionmat <- table(predsrfprobsamp,as.factor(data_test$quality))

confusionmat
               
predsrfprobsamp   3   4   5   6   7   8   9
              3   0   1   0   0   0   0   0
              4   0   6   6   0   0   0   0
              5   3  15 239  89   5   0   0
              6   1  10  94 324  69   9   0
              7   1   2   2  39  99  13   1
              8   0   0   0   1   0   8   0
              9   0   0   0   0   0   0   0
reshape2::melt(confusionmat) %>%
  ggplot(aes(x=predsrfprobsamp,y=Var2))+
  geom_tile(aes(fill=value), colour = "white") + 
   geom_text(aes(label = sprintf("%1.0f", value)), vjust = 1)+
  scale_fill_gradient(low = "blue", high = "red")+
  xlab(" Predicted Activity ")+ylab(" Actual Activity")+
  scale_y_discrete(limits=c('low','medium','high'))+
  scale_x_discrete(limits=c('high','medium','low'))+
  
  #scale_y_discrete(limits=c('three','six','seven','four','five','eight'))+
  #scale_x_discrete(limits=c('eight','five','four','seven','six','three'))+
  
  theme_bw()+ theme(legend.position = "none")

LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIFRvIE1hY2hpbmUgTGVhcm5pbmcgYW5kIERlZXAgTGVhcm5pbmcgd2l0aCBSLiBMQUIgMS4gVGhlIFdpbmUgUXVhbGl0eSBkYXRhc2V0IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7cn0KIyBGb3IgbWFuaXB1bGF0aW5nIHRoZSBkYXRhc2V0cwpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHJlYWRyKQoKIyBGb3IgcGxvdHRpbmcgY29ycmVsYXRpb24gbWF0cml4CmxpYnJhcnkoZ2djb3JycGxvdCkKCgojIE1hY2hpbmUgTGVhcm5pbmcgbGlicmFyeQpsaWJyYXJ5KGNhcmV0KQojIEZvciBNdWx0aS1jb3JlIHByb2Nlc3Npbmcgc3VwcG9ydApsaWJyYXJ5KGRvTUMpCiMgVXNlIDMgY29yZXMsIGNoYW5nZXQgaXQgYWNjb3JkaW5nbHkuIApyZWdpc3RlckRvTUMoY29yZXM9MykKCmBgYAojIEdFVCBUSEUgREFUQQojIyBMb2FkIHRoZSBkYXRhc2V0cwoKVEhFIFdJTkUgUVVBTElUWSBEQVRBU0VUCgpUaGUgdHdvIGRhdGFzZXRzIGFyZSByZWxhdGVkIHRvIHJlZCBhbmQgd2hpdGUgdmFyaWFudHMgb2YgdGhlIFBvcnR1Z3Vlc2UgIlZpbmhvIFZlcmRlIiB3aW5lLiBGb3IgbW9yZSBkZXRhaWxzLCBjb25zdWx0IHRoZSByZWZlcmVuY2UgW0NvcnRleiBldCBhbC4sIDIwMDldLiAKRHVlIHRvIHByaXZhY3kgYW5kIGxvZ2lzdGljIGlzc3Vlcywgb25seSBwaHlzaWNvY2hlbWljYWwgKGlucHV0cykgYW5kIHNlbnNvcnkgKHRoZSBvdXRwdXQpIHZhcmlhYmxlcyBhcmUgYXZhaWxhYmxlIChlLmcuIHRoZXJlIGlzIG5vIGRhdGEgYWJvdXQgZ3JhcGUgdHlwZXMsIHdpbmUgYnJhbmQsIHdpbmUgc2VsbGluZyBwcmljZSwgZXRjLikuCgpUaGVzZSBkYXRhc2V0cyBjYW4gYmUgdmlld2VkIGFzIGNsYXNzaWZpY2F0aW9uIG9yIHJlZ3Jlc3Npb24gdGFza3MuIFRoZSBjbGFzc2VzIGFyZSBvcmRlcmVkIGFuZCBub3QgYmFsYW5jZWQgKGUuZy4gdGhlcmUgYXJlIG11Y2ggbW9yZSBub3JtYWwgd2luZXMgdGhhbiBleGNlbGxlbnQgb3IgcG9vciBvbmVzKS4KCkF2YWlsYWJsZSBhdCBodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvZGF0YXNldHMvd2luZStxdWFsaXR5CgoKYGBge3J9CndpbmVkYXRhc2V0X2JsYW5jbyA8LSByZWFkX2NzdigiZGF0YS9ibGFuY29fdHJhaW4uY3N2Lmd6IikKd2luZWRhdGFzZXRfcmVkIDwtIHJlYWRfY3N2KCJkYXRhL3RpbnRvX3RyYWluLmNzdi5neiIpCgojIENyZWF0ZSBhIG5ldyBmZWF0dXJlIGZvciB0aGUgdHlwZSAKd2luZWRhdGFzZXRfYmxhbmNvJHR5cGU9IndoaXRlIgp3aW5lZGF0YXNldF9yZWQkdHlwZT0icmVkIgoKIyBNZXJnZSBib3RoIGRhdGFzZXRzIGludG8gb25lLgp3aW5lZGF0YXNldDwtcmJpbmQod2luZWRhdGFzZXRfYmxhbmNvLHdpbmVkYXRhc2V0X3JlZCkKCgoKIyBQcmludCB0aGUgZGF0YXNldAp3aW5lZGF0YXNldAoKCiN3aW5lZGF0YXNldCAlPiUgbWFwKGlzLm51bGwpCmBgYApgYGB7cn0Kd2luZWRhdGFzZXQgJT4lIGdyb3VwX2J5KHF1YWxpdHkpICU+JSBzdW1tYXJpc2UodG90YWw9bigpKQp3aW5lZGF0YXNldCAlPiUgZ3JvdXBfYnkoYHRvdGFsIHN1bGZ1ciBkaW94aWRlYCxxdWFsaXR5KSAgJT4lIHN1bW1hcmlzZSh0b3RhbD1uKCkpCmBgYAojIFZJU1VBTElaRSBUSEUgREFUQQoKYGBge3J9CnJlc2hhcGUyOjptZWx0KHdpbmVkYXRhc2V0KSAlPiUKZ2dwbG90KCkrCiAgZ2VvbV9ib3hwbG90KGFlcyh4PXZhcmlhYmxlLHk9dmFsdWUsZmlsbD12YXJpYWJsZSkpKwogIHRoZW1lX2J3KCkrIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgCiAgCiAgCnBsb3RseTo6Z2dwbG90bHkoKQpgYGAKCgoKIyMjIENvcnJlbGF0aW9uIE1hdHJpeApgYGB7cn0KI01hdHJpeiBkZSBjb3JyZWxhY2lvbgoKY29yX21hdHJpeDwtY29yKHdpbmVkYXRhc2V0ICU+JSBzZWxlY3QoLXR5cGUpKQpnZ2NvcnJwbG90KGNvcl9tYXRyaXgpCmBgYAoKYGBge3IgZmlnLndpZHRoPTEyfQpwYWlycyh3aW5lZGF0YXNldCAlPiUgc2VsZWN0KC10eXBlKSkKYGBgCgoKIyMjIEJveHBsb3Qgdm9sYXRpbGUKYGBge3J9CmdncGxvdCh3aW5lZGF0YXNldCkrCiAgZ2VvbV9ib3hwbG90KGFlcyh4PWFzLmZhY3RvcihxdWFsaXR5KSx5PWB2b2xhdGlsZSBhY2lkaXR5YCxmaWxsPWFzLmZhY3RvcihxdWFsaXR5KSkpKwogIHhsYWIoIlF1YWxpdHkiKSsKICB0aGVtZV9idygpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKICAKYGBgCiMjIyBCb3hwbG90IGFsY29ob2wKYGBge3J9CmdncGxvdCh3aW5lZGF0YXNldCkrCiAgZ2VvbV9ib3hwbG90KGFlcyh4PWFzLmZhY3RvcihxdWFsaXR5KSx5PWBhbGNvaG9sYCxmaWxsPWFzLmZhY3RvcihxdWFsaXR5KSkpKyAKICB4bGFiKCJRdWFsaXR5IikrCiAgdGhlbWVfYncoKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAojIENMRUFOLCBQUkVQQVJFICYgTUFOSVBVTEFURSBUSEUgREFUQQoKIyMgQ3JlYXRlIGNhdGVnb3JpY2FsIGZlYXR1cmVzIChvcHRpb25hbCkKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KdHJhaW5zZXQ8LXdpbmVkYXRhc2V0ICU+JSBtdXRhdGUodmluZWdhciA9IGlmZWxzZShgdm9sYXRpbGUgYWNpZGl0eWA8PTAuNCwnbG93JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShgdm9sYXRpbGUgYWNpZGl0eWA+MC40ICYgYHZvbGF0aWxlIGFjaWRpdHlgPD0wLjgsJ21lZGl1bScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnaGlnaCcpKSkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKGFsY29ob2xfbGV2ZWwgPSBpZmVsc2UoYGFsY29ob2xgPD05LCdsb3cnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGBhbGNvaG9sYD45ICYgYGFsY29ob2xgPD0xMSwnbWVkaXVtJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdoaWdoJykpKSAjJT4lICBzZWxlY3QoLWByZXNpZHVhbCBzdWdhcmAsLWBmaXhlZCBhY2lkaXR5YCwtYHZvbGF0aWxlIGFjaWRpdHlgLC1hbGNvaG9sLC1gZnJlZSBzdWxmdXIgZGlveGlkZWApCgoKCgpgYGAKCiMjIENyZWF0ZSBjYXRlZ29yeSBsYWJlbHMgZm9yIHF1YWxpdHkgKG9wdGlvbmFsKQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpzZXQuc2VlZCgxMCkKCnRyYWluc2V0IDwtIHdpbmVkYXRhc2V0ICU+JSBtdXRhdGUocXVhbGl0eT1pZmVsc2UocXVhbGl0eT09MywnbG93JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UocXVhbGl0eT09NCwnbG93JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UocXVhbGl0eT09NSwnbWVkaXVtJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UocXVhbGl0eT09NiwnbWVkaXVtJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UocXVhbGl0eT09NywnaGlnaCcsJ2hpZ2gnCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSkpKSkpICMlPiUgZmlsdGVyKHF1YWxpdHkgJWluJSBjKCdzZXZlbicsJ2ZpdmUnLCdzaXgnKSkKCgp0cmFpbnNldCAlPiUgZ3JvdXBfYnkocXVhbGl0eSkgJT4lIHN1bW1hcmlzZShuPW4oKSkgJT4lCiAgZ2dwbG90KCkrCiAgZ2VvbV9jb2woYWVzKHg9cXVhbGl0eSx5PW4sZmlsbD1xdWFsaXR5KSkrCiAgdGhlbWVfYncoKQoKYGBgCgoKIyMgTm8gZmVhdHVyZXMgY3JlYXRlZCwgcXVhbGl0eSBhcyBmYWN0b3IKYGBge3J9CnRyYWluc2V0IDwtIHdpbmVkYXRhc2V0CnRyYWluc2V0JHF1YWxpdHkgPC0gYXMuZmFjdG9yKHRyYWluc2V0JHF1YWxpdHkpCmBgYAoKCiMjIEVsaW1pbmF0ZSB0eXBlCmBgYHtyfQoKdHJhaW5zZXQgPC0gdHJhaW5zZXQgJT4lIHNlbGVjdCgtdHlwZSkKYGBgCgpgYGB7cn0KbmFtZXModHJhaW5zZXQpICU+JSBhcy5kYXRhLmZyYW1lKCkKYGBgCgojIFRSQUlOIFRIRSBNT0RFTAojIyBTcGxpdCB0cmFpbiBhbmQgdGVzdApgYGB7cn0KCnRyYWluSW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihhcy5mYWN0b3IodHJhaW5zZXQkcXVhbGl0eSksIHA9MC44MCwgbGlzdD1GQUxTRSkKZGF0YV90cmFpbiA8LSB0cmFpbnNldFsgdHJhaW5JbmRleCxdCmRhdGFfdGVzdCA8LSAgdHJhaW5zZXRbLXRyYWluSW5kZXgsXQpjb2xuYW1lcyhkYXRhX3RyYWluKSA8LSBtYWtlLm5hbWVzKGNvbG5hbWVzKGRhdGFfdHJhaW4pKQpjb2xuYW1lcyhkYXRhX3Rlc3QpIDwtIG1ha2UubmFtZXMoY29sbmFtZXMoZGF0YV90ZXN0KSkKCgpgYGAKIyMjIFBsb3QgY2xhc3MgZGlzdHJpYnV0aW9uIGluIHRyYWluCmBgYHtyfQpkYXRhX3RyYWluICAlPiUgZ3JvdXBfYnkocXVhbGl0eSkgJT4lIHN1bW1hcmlzZSh0b3RhbD1uKCkpICU+JQogIGdncGxvdCgpKwogIGdlb21fY29sKGFlcyh4PXF1YWxpdHkseT10b3RhbCxmaWxsPXF1YWxpdHkpKSsKICB0aGVtZV9jbGFzc2ljKCkKCmBgYAojIyMgUGxvdCBjbGFzcyBkaXN0cmlidXRpb24gaW4gdGVzdApgYGB7cn0KZGF0YV90ZXN0ICAlPiUgZ3JvdXBfYnkocXVhbGl0eSkgJT4lIHN1bW1hcmlzZSh0b3RhbD1uKCkpICU+JQogIGdncGxvdCgpKwogIGdlb21fY29sKGFlcyh4PXF1YWxpdHkseT10b3RhbCxmaWxsPXF1YWxpdHkpKSsKICB0aGVtZV9jbGFzc2ljKCkKYGBgCiMjIEZlYXR1cmUgc2VsZWN0aW9uCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CnJmZWNydGwgPC0gcmZlQ29udHJvbChmdW5jdGlvbnM9cmZGdW5jcywgbWV0aG9kPSJjdiIsIG51bWJlcj0xMCxhbGxvd1BhcmFsbGVsPVRSVUUpCnJlc3VsdHMgPC0gcmZlKHF1YWxpdHl+LiAsIGRhdGE9ZGF0YV90cmFpbiwgc2l6ZXM9YygxOjEzKSwgcmZlQ29udHJvbD1yZmVjcnRsKQpyZXN1bHRzCnByZWRpY3RvcnMocmVzdWx0cykKIyBwbG90IHRoZSByZXN1bHRzCnBsb3QocmVzdWx0cywgdHlwZT1jKCJnIiwgIm8iKSkKYGBgCgojIyBUcmFpbiBtb2RlbApgYGB7cn0KY3RybF9mYXN0IDwtIHRyYWluQ29udHJvbChtZXRob2Q9ImN2IiwgCiAgICAgICAgICAgICAgICAgICAgIHJlcGVhdHM9MSwKICAgICAgICAgICAgICAgICAgICAgbnVtYmVyPTUsIAogICAgICAgICAgICAgICAgICAgIyAgc3VtbWFyeUZ1bmN0aW9uPXR3b0NsYXNzU3VtbWFyeSwKICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZUl0ZXI9VCwKICAgICAgICAgICAgICAgICAgICAgY2xhc3NQcm9icz1GLAogICAgICAgICAgICAgICAgICAgICBhbGxvd1BhcmFsbGVsID0gVFJVRSkgIApgYGAKCmBgYHtyfQoKY3RybF9mYXN0JHNhbXBsaW5nPC0idXAiCgpzdm1HcmlkIDwtICBleHBhbmQuZ3JpZChzaWdtYT0gYygwLjAwMSwwLjAwMDEsMC4wMDAwMSksIAogICAgICAgICAgICAgICAgICAgICAgICBDID0gYygxLDIsNCw4LDE2LDMyLDY0LDgwLDEwMCwxMjApIAogICAgICAgICAgICAgICAgICAgICAgICApCgojc3ZtR3JpZCA8LSAgZXhwYW5kLmdyaWQoQz0gYygxMDApLCBzaWdtYSA9IGMoMSkpCgoKdHJhaW5fZm9ybXVsYTwtZm9ybXVsYShxdWFsaXR5fi4pCnJmRml0dXBzYW08LSB0cmFpbih0cmFpbl9mb3JtdWxhLAogICAgICAgICAgICAgICBkYXRhID0gZGF0YV90cmFpbiwKICAgICAgICAgICAgICAgI21ldGhvZCA9ICJyZiIsICAgIyBSYWRpYWwga2VybmVsCiAgICAgICAgICAgICAgICNtZXRob2QgPSAieGdiVHJlZSIsCiAgICAgICAgICAgICAgIG1ldGhvZCA9ICJyZiIsCiAgICAgICAgICAgICAgICN0dW5lTGVuZ3RoID0gOSwKICAgICAgICAgICAgICAgI3R1bmVHcmlkID0gc3ZtR3JpZCwKICAgICAgICAgICAgICAgI3ByZVByb2Nlc3M9Yygic2NhbGUiLCJjZW50ZXIiKSwKICAgICAgICAgICAgICAgI21ldHJpYz0iUk9DIiwKICAgICAgICAgICAgICAgI3dlaWdodHMgPSBtb2RlbF93ZWlnaHRzLAogICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBjdHJsX2Zhc3QpCgpwbG90KHJmRml0dXBzYW0pCnJmRml0dXBzYW0KcmZGaXR1cHNhbSRmaW5hbE1vZGVsCmBgYApgYGB7cn0KaW1wb3J0YW5jZSA8LSB2YXJJbXAocmZGaXR1cHNhbSwgc2NhbGU9RkFMU0UpCnBsb3QoaW1wb3J0YW5jZSkKYGBgCiMgVEVTVCBUSEUgREFUQQpgYGB7cn0KcHJlZHNyZnByb2JzYW1wPXByZWRpY3QocmZGaXR1cHNhbSxkYXRhX3Rlc3QpCiMgdXNlIGZvciByZWdyZXNpb24KI2NvbmZ1c2lvbk1hdHJpeChhcy5mYWN0b3IocHJlZHNyZnByb2JzYW1wICU+JSByb3VuZCgpKSxhcy5mYWN0b3IoZGF0YV90ZXN0JHF1YWxpdHkpKQoKY29uZnVzaW9uTWF0cml4KHByZWRzcmZwcm9ic2FtcCxhcy5mYWN0b3IoZGF0YV90ZXN0JHF1YWxpdHkpKQoKYGBgCgoKCgpgYGB7cn0KI2NvbmZ1c2lvbm1hdCA8LSB0YWJsZShwcmVkc3JmcHJvYnNhbXAgJT4lIHJvdW5kKCksYXMuZmFjdG9yKGRhdGFfdGVzdCRxdWFsaXR5KSkKCmNvbmZ1c2lvbm1hdCA8LSB0YWJsZShwcmVkc3JmcHJvYnNhbXAsYXMuZmFjdG9yKGRhdGFfdGVzdCRxdWFsaXR5KSkKCmNvbmZ1c2lvbm1hdApyZXNoYXBlMjo6bWVsdChjb25mdXNpb25tYXQpICU+JQogIGdncGxvdChhZXMoeD1wcmVkc3JmcHJvYnNhbXAseT1WYXIyKSkrCiAgZ2VvbV90aWxlKGFlcyhmaWxsPXZhbHVlKSwgY29sb3VyID0gIndoaXRlIikgKyAKICAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHNwcmludGYoIiUxLjBmIiwgdmFsdWUpKSwgdmp1c3QgPSAxKSsKICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJibHVlIiwgaGlnaCA9ICJyZWQiKSsKICB4bGFiKCIgUHJlZGljdGVkIFF1YWxpdHkgIikreWxhYigiIEFjdHVhbCBRdWFsaXR5IikrCiAgc2NhbGVfeV9kaXNjcmV0ZShsaW1pdHM9YygnbG93JywnbWVkaXVtJywnaGlnaCcpKSsKICBzY2FsZV94X2Rpc2NyZXRlKGxpbWl0cz1jKCdoaWdoJywnbWVkaXVtJywnbG93JykpKwogIAogICNzY2FsZV95X2Rpc2NyZXRlKGxpbWl0cz1jKCd0aHJlZScsJ3NpeCcsJ3NldmVuJywnZm91cicsJ2ZpdmUnLCdlaWdodCcpKSsKICAjc2NhbGVfeF9kaXNjcmV0ZShsaW1pdHM9YygnZWlnaHQnLCdmaXZlJywnZm91cicsJ3NldmVuJywnc2l4JywndGhyZWUnKSkrCiAgCiAgdGhlbWVfYncoKSsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCg==